package com.yuqih.common.config.impl;

import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.regex.Pattern;

import org.apache.commons.beanutils.BeanIntrospector;
import org.apache.commons.beanutils.DefaultBeanIntrospector;
import org.apache.commons.beanutils.IntrospectionContext;
import org.apache.commons.beanutils.PropertyUtilsBean;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.util.ClassUtils;

import com.yuqih.common.config.IFrameworkConfig;
import com.yuqih.common.config.IFrameworkConfigInitializing;
import com.yuqih.common.config.TypeDefinition;
import com.yuqih.common.dao.IBaseDao;
import com.yuqih.common.dao.IConditionHandler;
import com.yuqih.common.dao.ISortParser;
import com.yuqih.common.dto.TypeDescriptor;
import com.yuqih.common.service.IBaseService;

public class FrameworkConfig implements ApplicationContextAware, IFrameworkConfig, IFrameworkConfigInitializing {

	@Deprecated
	private Map<String, String> typeMapping;
	private List<TypeDefinition> definitions;

	private ApplicationContext applicationContext;

	private final List<TypeDefinition> typeDefinitions = new ArrayList<TypeDefinition>();
	private final Map<Class<?>, TypeDescriptor> modelDescriptorMapping = new HashMap<Class<?>, TypeDescriptor>();

	private String defaultMybatisNamespace;
	private Map<Pattern, String> mybatisNamespaceMapping;

	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		this.applicationContext = applicationContext;
	}

	public void init() throws Exception {
		initBeanIntrospector();
		initDefinitions();
		initContext();
	}
	
	private void initBeanIntrospector() {
		CustomBeanIntrospector.init();
	}

	private void initContext() {
		Collection<ISortParser> sortParsers = applicationContext.getBeansOfType(ISortParser.class).values();

		for (ISortParser sortParser : sortParsers) {
			registerSortParser(sortParser);
		}

		Collection<IConditionHandler> conditionHandlers = applicationContext.getBeansOfType(IConditionHandler.class)
				.values();
		for (IConditionHandler conditionHandler : conditionHandlers) {
			registerConditionHandler(conditionHandler);
		}
	}

	private void initDefinitions() throws Exception {
		if (definitions != null) {
			for (TypeDefinition definition : definitions) {
				check(definition);
				typeDefinitions.add(definition);
			}
		}

		if (typeMapping != null) {
			for (Entry<String, String> entry : typeMapping.entrySet()) {
				TypeDefinition typeDefinition = new TypeDefinition();
				typeDefinition.setModelType(Class.forName(entry.getKey()));
				typeDefinition.setEntityType(Class.forName(entry.getValue()));
				check(typeDefinition);
				typeDefinitions.add(typeDefinition);
			}
		}
	}

	private void check(TypeDefinition definition) {
		for (TypeDefinition typeDefinition : typeDefinitions) {
			if (Objects.equals(definition.getModelType(), typeDefinition.getModelType())) {
				throw new IllegalArgumentException("重复配置：" + definition.getModelType());
			}
			if (Objects.equals(definition.getEntityType(), typeDefinition.getEntityType())) {
				throw new IllegalArgumentException("重复配置：" + definition.getEntityType());
			}
		}
	}

	public void registerBaseService(Class<?> modelType, IBaseService<?, ?, ?> baseService, Class<?> conditionType) {
		TypeDefinition definition = null;
		for (TypeDefinition typeDefinition : typeDefinitions) {
			if (Objects.equals(typeDefinition.getModelType(), modelType)) {
				definition = typeDefinition;
			}
		}

		if (definition == null) {
			definition = new TypeDefinition();
			definition.setModelType(modelType);
			typeDefinitions.add(definition);
		}

		definition.setBaseService(baseService);
		if (definition.getConditionType() != null && !Objects.equals(conditionType, definition.getConditionType())) {
			throw new IllegalArgumentException(String.valueOf(conditionType) + "已被设置");
		}
		definition.setConditionType(conditionType);
	}

	public void registerBaseDao(Class<?> entityType, IBaseDao<?, ?, ?> baseDao, Class<?> conditionType) {
		TypeDefinition definition = null;
		for (TypeDefinition typeDefinition : typeDefinitions) {
			if (Objects.equals(typeDefinition.getEntityType(), entityType)) {
				definition = typeDefinition;
			}
		}

		if (definition == null) {
			definition = new TypeDefinition();
			definition.setEntityType(entityType);
			typeDefinitions.add(definition);
		}
		definition.setBaseDao(baseDao);
		if (definition.getConditionType() != null && !Objects.equals(conditionType, definition.getConditionType())) {
			throw new IllegalArgumentException(String.valueOf(conditionType) + "已被设置");
		}
		definition.setConditionType(conditionType);
	}

	public void registerSortParser(ISortParser sortParse) {
		TypeDefinition definition = null;
		for (TypeDefinition typeDefinition : typeDefinitions) {
			if (Objects.equals(typeDefinition.getEntityType(), sortParse.entityType())) {
				definition = typeDefinition;
			}
		}

		if (definition == null) {
			definition = new TypeDefinition();
			definition.setEntityType(sortParse.entityType());
			typeDefinitions.add(definition);
		}
		definition.setSortParser(sortParse);
	}

	public void registerConditionHandler(IConditionHandler conditionHandler) {
		TypeDefinition definition = null;
		for (TypeDefinition typeDefinition : typeDefinitions) {
			if (Objects.equals(typeDefinition.getEntityType(), conditionHandler.entityType())) {
				definition = typeDefinition;
			}
		}

		if (definition == null) {
			definition = new TypeDefinition();
			definition.setEntityType(conditionHandler.entityType());
			typeDefinitions.add(definition);
		}
		definition.setConditionHandler(conditionHandler);
	}

	@Override
	public Class<?> getEntityType(Class<?> modelType) {
		for (TypeDefinition typeDefinition : typeDefinitions) {
			if (Objects.equals(typeDefinition.getModelType(), modelType)) {
				return typeDefinition.getEntityType();
			}
		}

		return null;
	}

	@Override
	public Class<?> getModelType(Class<?> entityType) {
		for (TypeDefinition typeDefinition : typeDefinitions) {
			if (Objects.equals(typeDefinition.getEntityType(), entityType)) {
				return typeDefinition.getModelType();
			}
		}

		return null;
	}

	@Override
	public Class<?> getConditionType(Class<?> modelOrEntityType) {
		for (TypeDefinition typeDefinition : typeDefinitions) {
			if (Objects.equals(typeDefinition.getModelType(), modelOrEntityType)) {
				return typeDefinition.getConditionType();
			}
			if (Objects.equals(typeDefinition.getEntityType(), modelOrEntityType)) {
				return typeDefinition.getConditionType();
			}
		}

		return null;
	}

	@Override
	public String getMybatisNamespace(Class<?> entityType) {
		if (mybatisNamespaceMapping != null && !mybatisNamespaceMapping.isEmpty()) {
			for (Entry<Pattern, String> entry : mybatisNamespaceMapping.entrySet()) {
				if (entry.getKey().matcher(entityType.getName()).matches()) {
					return entry.getValue();
				}
			}
		}
		return defaultMybatisNamespace;
	}

	@Override
	public IBaseDao<?, ?, ?> getBaseDao(Class<?> modelOrEntityType) {
		for (TypeDefinition typeDefinition : typeDefinitions) {
			if (Objects.equals(typeDefinition.getModelType(), modelOrEntityType)) {
				return typeDefinition.getBaseDao();
			}
			if (Objects.equals(typeDefinition.getEntityType(), modelOrEntityType)) {
				return typeDefinition.getBaseDao();
			}
		}

		return null;
	}

	@Override
	public IBaseService<?, ?, ?> getBaseService(Class<?> modelType) {
		for (TypeDefinition typeDefinition : typeDefinitions) {
			if (Objects.equals(typeDefinition.getModelType(), modelType)) {
				return typeDefinition.getBaseService();
			}
		}

		return null;
	}

	@Override
	public TypeDescriptor getTypeDescriptor(Class<?> type) {
		TypeDescriptor modelDescriptor = modelDescriptorMapping.get(type);
		if (modelDescriptor == null) {
			modelDescriptor = buildModelDescriptor(type);
		}
		return modelDescriptor;
	}

	private synchronized TypeDescriptor buildModelDescriptor(Class<?> type) {
		TypeDescriptor modelDescriptor = modelDescriptorMapping.get(type);
		if (modelDescriptor == null) {
			modelDescriptor = TypeDescriptor.by(type);
			modelDescriptorMapping.put(type, modelDescriptor);
		}
		return modelDescriptor;
	}

	@Override
	public ISortParser getSortParser(Class<?> entityType) {
		for (TypeDefinition typeDefinition : typeDefinitions) {
			if (Objects.equals(typeDefinition.getEntityType(), entityType)) {
				return typeDefinition.getSortParser();
			}
		}

		return null;
	}

	@Override
	public IConditionHandler getConditionHandler(Class<?> entityType) {
		for (TypeDefinition typeDefinition : typeDefinitions) {
			if (Objects.equals(typeDefinition.getEntityType(), entityType)) {
				return typeDefinition.getConditionHandler();
			}
		}

		return null;
	}

	/**
	 * @param typeMapping
	 *            the typeMapping to set
	 */
	@Deprecated
	public void setTypeMapping(Map<String, String> typeMapping) {
		this.typeMapping = typeMapping;
	}

	/**
	 * @param definitions
	 *            the definitions to set
	 */
	public void setDefinitions(List<TypeDefinition> definitions) {
		this.definitions = definitions;
	}

	/**
	 * @param defaultMybatisNamespace
	 *            the defaultMybatisNamespace to set
	 */
	public void setDefaultMybatisNamespace(String defaultMybatisNamespace) {
		this.defaultMybatisNamespace = defaultMybatisNamespace;
	}

	/**
	 * @param mybatisNamespaceMapping
	 *            the mybatisNamespaceMapping to set
	 */
	public void setMybatisNamespaceMapping(Map<String, String> mapping) {
		if (mapping == null) {
			this.mybatisNamespaceMapping = null;
			return;
		}

		this.mybatisNamespaceMapping = new LinkedHashMap<Pattern, String>();
		for (Entry<String, String> entry : mapping.entrySet()) {
			mybatisNamespaceMapping.put(Pattern.compile(entry.getKey()), entry.getValue());
		}

	}
	
	/**
	 * 在使用cglib的情况下，PropertyUtils的copyProperties方法报错，此类解决了这个问题
	 * 
	 * @author yuqi huang
	 *
	 */
	private static class CustomBeanIntrospector extends PropertyUtilsBean implements BeanIntrospector {
		
		static void init() {
			PropertyUtilsBean bean = PropertyUtilsBean.getInstance();
			bean.removeBeanIntrospector(DefaultBeanIntrospector.INSTANCE);
			bean.addBeanIntrospector(new CustomBeanIntrospector());
		}

		@Override
		public void introspect(IntrospectionContext icontext) throws IntrospectionException {
			Class<?> targetClass = icontext.getTargetClass();
			if (ClassUtils.isCglibProxyClass(targetClass)) {
				icontext = new IntrospectionContextWrapper(icontext, targetClass.getSuperclass());
			}
			DefaultBeanIntrospector.INSTANCE.introspect(icontext);
		}
		
	}
	
	private static class IntrospectionContextWrapper implements IntrospectionContext {
		
		private final IntrospectionContext instance;
		private final Class<?> targetClass;
		
		

		/**
		 * @param instance
		 * @param targetClass
		 */
		public IntrospectionContextWrapper(IntrospectionContext instance, Class<?> targetClass) {
			super();
			this.instance = instance;
			this.targetClass = targetClass;
		}

		@Override
		public Class<?> getTargetClass() {
			return targetClass;
		}

		@Override
		public void addPropertyDescriptor(PropertyDescriptor desc) {
			instance.addPropertyDescriptor(desc);
			
		}

		@Override
		public void addPropertyDescriptors(PropertyDescriptor[] descriptors) {
			instance.addPropertyDescriptors(descriptors);
			
		}

		@Override
		public boolean hasProperty(String name) {
			return instance.hasProperty(name);
		}

		@Override
		public PropertyDescriptor getPropertyDescriptor(String name) {
			return instance.getPropertyDescriptor(name);
		}

		@Override
		public void removePropertyDescriptor(String name) {
			instance.removePropertyDescriptor(name);
			
		}

		@Override
		public Set<String> propertyNames() {
			return instance.propertyNames();
		}
		
	}

}
